<?php
	/**
	  * @name		CombinationsBuilder
	  * @brief		Clase para generar combinaciones de una lista de elementos
	  * @author		Hermann D. Schimpf <hschimpf@gschimpf.com>
	  * @license	http://www.gnu.org/licenses/gpl.html
	  *				CombinationsBuilder - Clase para generar combinaciones de una lista de elementos
	  *				Copyright © 2010 Hermann D. Schimpf
	  *				Este programa es software libre: usted puede redistribuirlo y/o modificarlo conforme a los términos de la Licencia Pública General de GNU publicada por la Fundación para el Software Libre, ya sea la versión 3 de esta Licencia o (a su elección) cualquier versión posterior.
	  *				Este programa se distribuye con el deseo de que le resulte útil, pero SIN GARANTÍAS DE NINGÚN TIPO; ni siquiera con las garantías implícitas de COMERCIABILIDAD o APTITUD PARA UN PROPÓSITO DETERMINADO. Para más información, consulte la Licencia Pública General de GNU.
	  *				Junto con este programa, se debería incluir una copia de la Licencia Pública General de GNU. De no ser así, ingrese a http://www.gnu.org/licenses
	  * @date		2010-02-20
	  * @version	0.1.6
  	**/

	class CombinationsBuilder extends MyDebuger implements MyVersions {
		/**
		  * Version de la clase
		  * @access	private
		  * @static
		  * @name	$version
		**/
		private static $version			= 0;
		/**
		  * Subversion de la clase
		  * @access	private
		  * @static
		  * @name	$subversion
		**/
		private static $subversion		= 1;
		/**
		  * Revision de la clase
		  * @access	private
		  * @static
		  * @name	$revision
		**/
		private static $revision		= 6;

		/**
		  * Elementos a procesar
		  * @access	private
		  * @name	$elementsList
		**/
		private $elementsList		= Array();
		/**
		  * Lista de combinaciones
		  * @access	private
		  * @name	$combinations
		**/
		private $combinations		= Array();
		/**
		  * Cantidad de combinaciones
		  * @access	private
		  * @name	$combinationsCount
		**/
		private $combinationsCount	= 1;
		/**
		  * Bandera para repetir los elementos en las combinaciones
		  * @access	private
		  * @name	$repeat
		**/
		private $repeat				= True;

		/**
		  * Constructor de la clase
		  * @access	public
		  * @param	boolean	$debug		Valor booleano para habilitar la depuracion de la clase
		  * @return	void		Este metodo no retorna valores
		 **/
		public function __construct($debug = False) {
			/* creamos el padre */
			parent::__construct($debug, __CLASS__, 300, 512);
		}

		/**
		  * Destructor de la clase
		  * @access	public
		  * @return	void		Este metodo no retorna valores
		 **/
		public function __destruct() {
			/* vaciamos el cache */
			unset($this->combinations);
			/* matamos el padre */
			parent::__destruct();
		}

		/**
		  * Metodo para obtener la version de la clase
		  * @access	public
		  * @static
		  * @return	String	Retorna la version de la clase con el formato: v{version}.{subversion}.{revision}
		 **/
		public static final function getVersion() {
			/* retornamos la version actual de la clase */
			return 'v' . self::$version . '.' . self::$subversion . '.' . self::$revision;
		}

		/**
		  * Metodo para obtener las combinaciones de los elementos
		  * @access	public
		  * @static
		  * @param	Array		$elementList	Lista de elementos
		  * @param	Boolean	$repeat			Bandera para repetir los elementos en las combinaciones
		  * @param	Boolean	$debug			Bandera para habilitar la depuracion
		  * @return	Array		Retorna la lista de las combinaciones
		 **/
		public static function getCombinations($elementsList = Null, $repeat = True, $debug = False) {
			try {
				try {
					/* creamos una instancia */
					$getCombinations = new CombinationsBuilder($debug);
					/* verificamos los valores recibidos */
					$getCombinations->verificarValoresRecibidos($elementsList);
					/* almacenamos los elementos recibidos */
					$getCombinations->saveElements($elementsList);
					/* almacenamos la cantidad de combinaciones que debemos realizar */
					$getCombinations->saveCombinationsCount($repeat);
					/* ejecutamos el proceso */
					$getCombinations->runProcess($repeat);
					/* retornamos las combinaciones */
					return $getCombinations->getResult();
				} catch (Exception $e) {
					/* capturamos todos los errores y los transformamos en mi excepcion */
					$getCombinations->convertToMyException($e);
				}
			} catch (MyException $e) {
				/* si se produjo una excepcion la manejamos con el metodo 'show' */
				$getCombinations->show($e->getMensaje());
			}
		}

		/**
		  * Metodo para verificar los valores especificados
		  * @access	private
		  * @param	Array		$elementList	Lista de elementos
		  * @return	Void		Este metodo no retorna resultados
		 **/
		private function verificarValoresRecibidos($elementsList) {
			/* mostramos un mensaje */
			$this->show('Verificando valores recibidos..', False);
			/* verificamos si especifico algun valor */
			if ($elementsList === Null)
				/* salimos con una excepcion */
				$this->newException('No se especificaron los elementos a procesar');
			/* verificamos si los elementos se pasaron por medio de un array */
			if (gettype($elementsList) !== 'array')
				/* salimos con una excepcion */
				$this->newException('Los elementos se deben enviar en un array');
			/* mostramos ok al mensaje */
			$this->showOK();
		}

		/**
		  * Metodo para almacenar los elementos en un atributo privado
		  * @access	private
		  * @param	Array		$elementList	Lista de elementos
		  * @return	Void		Este metodo no retorna resultados
		 **/
		private function saveElements($elementsList) {
			/* almacenamos los elementos recibidos */
			$this->elementsList = $elementsList;
			/* mostramos un mensaje */
			$this->show('Cantidad de elementos almacenados: ' . $this->bold(count($this->elementsList)));
		}

		private function saveCombinationsCount($repeat) {
			/* mostramos un mensaje */
			$this->show('Obteniendo cantidad de combinaciones a crear..', False);
			/* verificamos si hay que repetir los elementos */
			if ($repeat === True)
				/* almacenamos la cantidad de elementos */
				$this->combinationsCount = count($this->getElements());
			/* ingresamos a un bucle */
			for ($i = 1; $i <= (count($this->getElements()) - ($repeat === True ? 1 : 0)); $i++)
				/* almacenamos la cantidad de combinaciones posibles */
				$this->combinationsCount *= ($repeat === True ? count($this->getElements()) : $i);
			/* mostramos ok al mensaje */
			$this->showOK();
			/* mostramos la cantidad de combinaciones obtenidas */
			$this->show('Cantidad de combinaciones a crear: ' . $this->bold($this->combinationsCount));
		}

		private function runProcess($repeat) {
			/* mostramos un mensaje */
			$this->show('Iniciando proceso de combinacion..');
			/* almacenamos la bandera */
			$this->repeat = $repeat;
			/* ejecutamos el proceso */
			$this->combinar();
			/* mostramos la cantidad de combinaciones obtenidas */
			$this->show('Combinaciones obtenidas: ' . $this->bold(count($this->getResult())));
		}

		private function combinar($combination = Array()) {
			/* obtenemos la posicion */
			$pos = count($combination);
			/* recorremos los elementos */
			foreach($this->getElements() AS $element) {
				/* verificamos si podemos repetir */
				if ($this->repeat === False && in_array($element, $combination))
					/* saltamos al siguiente */
					continue;
				/* agregamos el elemento al grupo en la posicion */
				$combination[$pos] = $element;
				/* verificamos si ya estamos en el final */
				if (count($this->getElements()) == count($combination))
					/* agregamos la combinacion al resultado final */
					$this->addCombination($combination);
				else
					/* volvemos a llamar al proceso con la combinacion actual */
					$this->combinar($combination);
			}
		}

		private function addCombination($combination) {
			/* agregamos la combinacion al resultado final */
			$this->combinations[] = $combination;
		}

		private function getElements() {
			/* retornamos la lista de elementos */
			return $this->elementsList;
		}

		private function getResult() {
			/* retornamos las combinaciones */
			return $this->combinations;
		}
	}
?>